//
// Created by hjie on 23-8-16.
//

#include "input_output_context.h"
#include "base_util.h"
#include "codec_config.h"
#include "hw_instance.h"
#include "input_output_operate.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <libavutil/dict.h>
#include <libavutil/mem.h>
#include <memory>
#include <sstream>
#include <string>
#include "media_stream.h"
#include "codec_context.h"


StreamData::StreamData(int index, AVStream *stream)
{
    m_stream_data  = stream;
    m_stream_index = index;
    m_pts_time     = stream->start_time * av_q2d(stream->time_base);
}

StreamData::~StreamData()
{

}

InputFileData::InputFileData()
{
    m_format_ctx = nullptr;

    m_video_stream_index = -1;
    m_audio_stream_index = -1;

    m_hw_buffer_ref = nullptr;

    m_video_decoder_ctx = nullptr;
    m_audio_decoder_ctx = nullptr;

    m_read_packet = nullptr;
    m_decoder_frame = nullptr;

    m_hw_pixel_fmt = AV_PIX_FMT_NONE;
}

InputFileData::~InputFileData()
{
    if (m_hw_buffer_ref)
    {
        av_buffer_unref(&m_hw_buffer_ref);
        m_hw_buffer_ref = nullptr;
    }
    if (m_video_decoder_ctx)
    {
        avcodec_close(m_video_decoder_ctx);
        avcodec_free_context(&m_video_decoder_ctx);
    }
    if (m_audio_decoder_ctx)
    {
        avcodec_close(m_audio_decoder_ctx);
        avcodec_free_context(&m_audio_decoder_ctx);
    }
    if (m_format_ctx)
    {
        avformat_close_input(&m_format_ctx);
        avformat_free_context(m_format_ctx);
    }
    if (m_read_packet)
    {
        av_packet_unref(m_read_packet);
        av_packet_free(&m_read_packet);
    }
    if (m_decoder_frame)
    {
        av_frame_unref(m_decoder_frame);
        av_frame_free(&m_decoder_frame);
    }
}

bool InputFileData::OpenFile(const std::string &filename)
{
    m_format_ctx = avformat_alloc_context();

    int result = avformat_open_input(&m_format_ctx, filename.c_str(), NULL, NULL);
    if (result < 0)
    {
        return false;
    }
    m_input_filename = filename;

    avformat_find_stream_info(m_format_ctx, NULL);
    av_dump_format(m_format_ctx, 0, m_input_filename.c_str(), 0);

    for (unsigned int i = 0; i < m_format_ctx->nb_streams; i++)
    {
        AVStream * stream = m_format_ctx->streams[i];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_video_stream_index = i;
        }
        else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            m_audio_stream_index = i;
        }
    }
    return true;
}

bool InputFileData::OpenDecoderCtx(AVMediaType media_type)
{
    int index = media_type == AVMEDIA_TYPE_AUDIO ? m_audio_stream_index : m_video_stream_index;

    const AVCodec * codec = avcodec_find_decoder(m_format_ctx->streams[index]->codecpar->codec_id);
    if (!codec)
    {
        return false;
    }
    AVCodecContext * codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        return false;
    }
    avcodec_parameters_to_context(codec_ctx, m_format_ctx->streams[index]->codecpar);
    codec_ctx->pkt_timebase = m_format_ctx->streams[index]->time_base;

    switch (media_type)
    {
        case AVMEDIA_TYPE_VIDEO:
        {
            m_video_decoder_ctx = codec_ctx;
            break;
        }
        case AVMEDIA_TYPE_AUDIO:
        {
            m_audio_decoder_ctx = codec_ctx;
            break;
        }
        default:
            break;
    }
    int result = 0;
    if ((result = avcodec_open2(codec_ctx, codec, nullptr)) < 0)
    {
        PrintLog(result);
        return false;
    }
    return true;
}

bool InputFileData::InitHwDecoder(AVMediaType media_type, enum AVHWDeviceType hw_device_type)
{
    std::string device_name;
    const char * ptr = nullptr;
    if (hw_device_type == AV_HWDEVICE_TYPE_QSV)
    {
        device_name = "auto";
        ptr = device_name.c_str();
    }
    else if (hw_device_type == AV_HWDEVICE_TYPE_VAAPI)
    {
        ptr = nullptr;
    }
    int result = av_hwdevice_ctx_create(&m_hw_buffer_ref, hw_device_type, ptr, NULL, 0);
    if (result < 0)
    {
        return false;
    }
    if (media_type == AVMEDIA_TYPE_VIDEO)
    {
        m_video_decoder_ctx->hw_device_ctx = av_buffer_ref(m_hw_buffer_ref);
        m_video_decoder_ctx->opaque        = this;
        m_video_decoder_ctx->get_format    = GetHwPixelFormat;
    }
    else if (media_type == AVMEDIA_TYPE_AUDIO)
    {
        m_audio_decoder_ctx->hw_device_ctx = av_buffer_ref(m_hw_buffer_ref) ;
    }
    return true;
}

bool InputFileData::OpenDecoderCtx(AVMediaType media_type, AVHWDeviceType hw_device_type)
{
    int index = media_type == AVMEDIA_TYPE_AUDIO ? m_audio_stream_index : m_video_stream_index;

    const AVCodec *p, * decoder = nullptr;
    void *i = 0;

    while ((p = av_codec_iterate(&i)))
    {
        if (!p)
        {
            continue;
        }
        if ((p->decode == nullptr && p->receive_frame == nullptr) || p->type != media_type)
        {
            continue;
        }
        if (hw_device_type != AV_HWDEVICE_TYPE_NONE && p->hw_configs && p->init && p->decode && p->close &&
            p->id == m_format_ctx->streams[m_video_stream_index]->codecpar->codec_id)
        {
            for (int codec_index = 0; ;codec_index++)
            {
                const AVCodecHWConfig * config = avcodec_get_hw_config(p, codec_index);
                if (!config)
                {
                    break;
                }
                if (config->device_type == hw_device_type)
                {
                    m_hw_pixel_fmt = config->pix_fmt;
                    m_hw_config_method = config->methods;
                    decoder = p;
                    break;
                }
            }
        }
        if (m_hw_pixel_fmt != AV_PIX_FMT_NONE && decoder)
        {
            break;
        }
    }
    AVDictionary * opts = NULL;
    if (!decoder)
    {
        decoder = avcodec_find_decoder(m_format_ctx->streams[index]->codecpar->codec_id);
    }
    else
    {
        av_dict_set(&opts, "threads", "auto", 0);
    }
    if (!decoder)
    {
        return false;
    }
    AVCodecContext * codec_ctx = avcodec_alloc_context3(decoder);
    if (!codec_ctx)
    {
        return false;
    }
    avcodec_parameters_to_context(codec_ctx, m_format_ctx->streams[index]->codecpar);
    codec_ctx->pkt_timebase = m_format_ctx->streams[index]->time_base;
    codec_ctx->pix_fmt      = m_hw_pixel_fmt;

    bool result = true;
    switch (media_type)
    {
        case AVMEDIA_TYPE_VIDEO:
        {
            m_video_decoder_ctx = codec_ctx;
            if (m_hw_config_method & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
            {
                result = InitHwDecoder(media_type, hw_device_type);
            }
            else if (m_hw_config_method & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
            {
                int ret = av_hwdevice_ctx_create(&m_hw_buffer_ref, hw_device_type, "auto", nullptr, 0);
                if (ret >= 0)
                {
                    m_video_decoder_ctx->opaque = (void*)this;
                    m_video_decoder_ctx->get_format = GetFormatForInputHwFrameCtx;
                    result = true;
                }
                else
                {
                    result = false;
                }
            }
            else
            {

            }
            break;
        }
        case AVMEDIA_TYPE_AUDIO:
        {
            m_audio_decoder_ctx = codec_ctx;
            m_audio_decoder_ctx->hw_device_ctx = nullptr;
            break;
        }
        default:
            break;
    }
    if (result && avcodec_open2(codec_ctx, decoder, nullptr) < 0)
    {
        return false;
    }
    return true;
}

bool InputFileData::InitDecoderCtx()
{
    bool result = true;
    if (m_video_stream_index >= 0)
    {
        result = OpenDecoderCtx(AVMEDIA_TYPE_VIDEO);
    }
    if (result && m_audio_stream_index >= 0)
    {
        result = OpenDecoderCtx(AVMEDIA_TYPE_AUDIO);
    }
    if (result)
    {
        m_read_packet = av_packet_alloc();
        m_decoder_frame = av_frame_alloc();
    }
    return result;
}

bool InputFileData::InitDecoderCtxWithHwType(AVHWDeviceType hw_device_type)
{
    bool result = true;
    if (m_video_stream_index >= 0)
    {
        result = OpenDecoderCtx(AVMEDIA_TYPE_VIDEO, hw_device_type);
    }
    if (result && m_audio_stream_index >= 0)
    {
        result = OpenDecoderCtx(AVMEDIA_TYPE_AUDIO);
    }
    if (result)
    {
        m_read_packet = av_packet_alloc();
        m_decoder_frame = av_frame_alloc();
    }
    return result;
}

bool InputFileData::InitDecoderCtxWithHwName(const std::string &hw_name)
{
    AVHWDeviceType hw_device_type = av_hwdevice_find_type_by_name(hw_name.c_str());
    if (hw_device_type == AV_HWDEVICE_TYPE_NONE)
    {
        return false;
    }
    return InitDecoderCtxWithHwType(hw_device_type);
}

OutputFileData::OutputFileData()
{
    m_output_fmt_ctx = nullptr;

    m_audio_stream_index = -1;
    m_audio_stream_index = -1;

    m_audio_encoder_ctx = nullptr;
    m_video_encoder_ctx = nullptr;

    m_hw_buffer_ref = nullptr;
}

OutputFileData::~OutputFileData()
{
    if (m_hw_buffer_ref)
    {
        av_buffer_unref(&m_hw_buffer_ref);
        m_hw_buffer_ref = nullptr;
    }
    if (m_video_encoder_ctx)
    {
        avcodec_close(m_video_encoder_ctx);
        avcodec_free_context(&m_video_encoder_ctx);
    }
    if (m_audio_encoder_ctx)
    {
        avcodec_close(m_audio_encoder_ctx);
        avcodec_free_context(&m_audio_encoder_ctx);
    }
    if (m_output_fmt_ctx)
    {
        if (!(m_output_fmt_ctx->oformat->flags & AVFMT_NOFILE))
        {
            avio_closep(&m_output_fmt_ctx->pb);
        }
        avformat_close_input(&m_output_fmt_ctx);
        avformat_free_context(m_output_fmt_ctx);
    }
    if (m_recv_packet)
    {
        av_packet_unref(m_recv_packet);
        av_packet_free(&m_recv_packet);
    }
    if (m_encoder_frame)
    {
        av_frame_unref(m_encoder_frame);
        av_frame_free(&m_encoder_frame);
    }
}

bool OutputFileData::InitOutputFmtCtx(const std::string &url, AVBufferRef * buffer)
{
    int result = avformat_alloc_output_context2(&m_output_fmt_ctx, NULL, NULL, url.c_str());
    if (result < 0)
    {
        PrintLog(result);
        return false;
    }
    m_output_url = url;

    m_recv_packet = av_packet_alloc();
    m_encoder_frame = av_frame_alloc();
    return result >= 0;
}

bool OutputFileData::CreateVideoNewStream(InputFileData *input_data, const std::string &hw_name, CodecConfig * codec_config)
{
    AVHWDeviceType hw_device_type = av_hwdevice_find_type_by_name(hw_name.c_str());
    if (!hw_name.empty() && hw_device_type == AV_HWDEVICE_TYPE_NONE)
    {
        printf("no found device type %s.\n", hw_name.c_str());
        return false;
    }
    bool result = true;
    for (unsigned int index = 0; index < input_data->m_format_ctx->nb_streams; index++)
    {
        AVStream * input_stream = input_data->m_format_ctx->streams[index];
        if (input_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            const AVCodec * codec = nullptr;
            if (hw_name.empty())
            {
                if (codec_config)
                {
                    codec = avcodec_find_encoder_by_name(codec_config->m_codec_name.c_str());
                }
            }
            else
            {
                codec = GetAvCodecById(input_stream->codecpar->codec_id, hw_device_type);
            }
            if (!codec)
            {
                printf("no found AVCodec name\n");
                result = false;
                break;
            }
            AVCodecContext * codec_context = avcodec_alloc_context3(codec);
            if (!codec_context)
            {
                result = false;
                break;
            }
            m_video_encoder_ctx   = codec_context;
            avcodec_parameters_to_context(codec_context, input_stream->codecpar);

            if (input_stream->codecpar->codec_id != codec->id)
            {
                // input codec and output codec is diff
                codec_context->codec_id  = codec->id;
                codec_context->codec_tag = SelectCodecTagByCodecId(codec_context->codec_id);
            }
            // must
            codec_context->time_base    = input_stream->time_base;
            // must
            codec_context->framerate = input_stream->avg_frame_rate;

            if (codec_config)
            {
                VideoCtxOperateCodecConfig(*codec_config);
            }

            if (m_output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
                codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

            // process hw device
            if (!hw_name.empty() && m_hw_config_method & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
            {
                if (m_hw_buffer_ref)
                {
                    av_buffer_unref(&m_hw_buffer_ref);
                }
                AVStream * new_stream = avformat_new_stream(m_output_fmt_ctx, nullptr);
                m_hw_buffer_ref = nullptr;
                int ret = av_hwdevice_ctx_create(&m_hw_buffer_ref, hw_device_type, nullptr, nullptr, 0);
                if (ret >= 0)
                {
                    m_video_encoder_ctx->hw_device_ctx = av_buffer_ref(m_hw_buffer_ref);
                    m_video_encoder_ctx->pix_fmt       = m_hw_pixel_fmt;
                    ret = avcodec_open2(codec_context, codec, nullptr);
                    if (ret < 0)
                    {
                        PrintLog(ret);
                        result = false;
                        break;
                    }
                    else
                    {
                        avcodec_parameters_from_context(new_stream->codecpar, codec_context);
                        new_stream->time_base = codec_context->time_base;
                        new_stream->codecpar->format = m_hw_pixel_fmt;
                        m_video_encoder_ctx   = codec_context;
                        m_video_stream_index  = new_stream->index;

                        new_stream->avg_frame_rate = input_stream->avg_frame_rate;
                        new_stream->r_frame_rate = input_stream->r_frame_rate;

                        m_get_format_init_status = true;

                        result = true;
                        break;
                    }
                }
            }
            else if (!hw_name.empty() && m_hw_config_method & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
            {
                m_get_format_init_status = false;
                input_data->m_output_data = this;
                input_data->m_video_decoder_ctx->opaque = input_data;
                m_video_codec_for_hw = codec;
            }
            else if (hw_name.empty())
            {

                AVStream * new_stream = avformat_new_stream(m_output_fmt_ctx, nullptr);

                AVDictionary * thread_opt = nullptr;
                
                av_dict_set(&thread_opt, "threads", "auto", 0);

                int ret = avcodec_open2(codec_context, codec, &thread_opt);
                if (ret < 0)
                {
                    PrintLog(ret);
                    result = false;
                    // for debug
                    // printf("codec id : %d and codec type : %d 
                    //         codec name : %s codec_id : %d.\n", codec_context->codec_id, codec_context->codec_type, 
                    //                                            codec_context->codec->name, codec_context->codec->id);
                    break;
                }
                else
                {
                    avcodec_parameters_from_context(new_stream->codecpar, codec_context);
                    new_stream->time_base = codec_context->time_base;
                    new_stream->codecpar->format = m_hw_pixel_fmt;
                    m_video_encoder_ctx   = codec_context;
                    m_video_stream_index  = new_stream->index;

                    new_stream->avg_frame_rate = input_stream->avg_frame_rate;
                    new_stream->r_frame_rate = input_stream->r_frame_rate;
                    m_get_format_init_status = true;
                }
            }
            break;
        }
    }
    return result;
}

bool OutputFileData::CreateAudioNewStream(InputFileData *input_data, const std::string & hw_name, CodecConfig * codec_config)
{
    bool result = true;
    for (unsigned int index = 0; index < input_data->m_format_ctx->nb_streams; index++)
    {
        AVStream * input_stream = input_data->m_format_ctx->streams[index];
        if (input_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            AVStream * new_stream = avformat_new_stream(m_output_fmt_ctx, NULL);
            const AVCodec * codec = avcodec_find_encoder(input_stream->codecpar->codec_id);
            AVCodecContext * codec_ctx = avcodec_alloc_context3(codec);
            avcodec_parameters_to_context(codec_ctx, input_stream->codecpar);

            codec_ctx->time_base    = input_stream->time_base;
            codec_ctx->framerate    = input_stream->avg_frame_rate;

            if (codec_config)
            {
                AudioCtxOperateCodecConfig(*codec_config);
            }
            int ret = 0;
            if ((ret = avcodec_open2(codec_ctx, codec, nullptr)) < 0)
            {
                result = false;
                PrintLog(ret);
                break;
            }
            avcodec_parameters_from_context(new_stream->codecpar, codec_ctx);
            new_stream->time_base = codec_ctx->time_base;
            m_audio_encoder_ctx   = codec_ctx;
            m_audio_stream_index  = new_stream->index;

            break;
        }
    }
    return result;
}

bool OutputFileData::OpenOutputFile()
{
    bool result = true;
    if (!(m_output_fmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        // open file
        int ret = avio_open(&m_output_fmt_ctx->pb, m_output_url.c_str(), AVIO_FLAG_WRITE);
        result = ret >= 0;
    }
    return result;
}

bool OutputFileData::WriterHeader()
{
    if (!OpenOutputFile())
    {
        return false;
    }
    int result = avformat_write_header(m_output_fmt_ctx, nullptr);
    if (result < 0)
    {
        PrintLog(result);
        return false;
    }
    return true;
}

const AVCodec *OutputFileData::GetAvCodecById(AVCodecID id, AVHWDeviceType device_type)
{
    m_hw_pixel_fmt = AV_PIX_FMT_NONE;
    const AVCodec *p, * encoder = nullptr;
    void *i = 0;

    while ((p = av_codec_iterate(&i)))
    {
        if (!p)
        {
            continue;
        }
        if (p->id != id || (p->encode2 == nullptr && p->encode_sub == nullptr && p->receive_packet == nullptr))
        {
            continue;
        }
        if (device_type != AV_HWDEVICE_TYPE_NONE && p->hw_configs && p->init && p->close)
        {
            for (int codec_index = 0; ;codec_index++)
            {
                const AVCodecHWConfig * config = avcodec_get_hw_config(p, codec_index);
                if (!config)
                {
                    break;
                }
                if (config->device_type == device_type)
                {
                    m_hw_pixel_fmt = config->pix_fmt;
                    m_hw_config_method = config->methods;
                    encoder = p;
                    break;
                }
            }
        }
        if (m_hw_pixel_fmt != AV_PIX_FMT_NONE && encoder)
        {
            break;
        }
    }
    return encoder;
}

void OutputFileData::VideoCtxOperateCodecConfig(CodecConfig codec_config)
{
    auto encoder_ctx = m_video_encoder_ctx;
    if (!encoder_ctx)
    {
        return;
    }
    if (codec_config.m_timebase.num > 0 && codec_config.m_timebase.den > 0)
    {
        encoder_ctx->time_base = codec_config.m_timebase;
    }
    if (codec_config.m_r_frame_rate.num > 0 && codec_config.m_r_frame_rate.den > 0)
    {
        encoder_ctx->framerate = codec_config.m_r_frame_rate;
    }
    if (codec_config.m_width > 0)
    {
        encoder_ctx->width = codec_config.m_width;
    }
    if (codec_config.m_height > 0)
    {
        encoder_ctx->height = codec_config.m_height;
    }
    if (codec_config.m_format != AV_PIX_FMT_NONE)
    {
        encoder_ctx->pix_fmt = (enum AVPixelFormat)codec_config.m_format;
    }
    if (codec_config.m_sw_format != AV_PIX_FMT_NONE)
    {
        encoder_ctx->sw_pix_fmt = (enum AVPixelFormat)codec_config.m_sw_format;
    }
    if (codec_config.m_bit_rate)
    {
        encoder_ctx->bit_rate = codec_config.m_bit_rate;
    }
    if (codec_config.m_has_b_frame != -1)
    {
        encoder_ctx->has_b_frames = codec_config.m_has_b_frame;
    }   
}

void OutputFileData::AudioCtxOperateCodecConfig(CodecConfig codec_config)
{
    auto encoder_ctx = m_audio_encoder_ctx;
    if (!encoder_ctx)
    {
        return;
    }
}


InputFormatContext::InputFormatContext()
{
    m_format_ctx = nullptr;
    m_audio_decoder_ctx = nullptr;
    m_video_decoder_ctx = nullptr;
}

InputFormatContext::~InputFormatContext()
{
    if (m_format_ctx)
    {
        avformat_close_input(&m_format_ctx);
        avformat_free_context(m_format_ctx);
        m_format_ctx = nullptr;
        PrintLog("release format source");
    }
}

bool InputFormatContext::OpenUrlWithFindStreamInfo(const std::string &file_or_stream)
{
    int result = avformat_open_input(&m_format_ctx, file_or_stream.c_str(), nullptr, nullptr);
    if (result < 0)
    {
        PrintLog(result);
        avformat_free_context(m_format_ctx);
        m_format_ctx = nullptr;
        return false;
    }
    avformat_find_stream_info(m_format_ctx, nullptr);
    
    av_dump_format(m_format_ctx, 0, file_or_stream.c_str(), 0);

    ProcessMediaStream();

    return true;
}

void InputFormatContext::ProcessMediaStream()
{
    for (unsigned int index = 0; index < m_format_ctx->nb_streams; index++)
    {
        auto stream = m_format_ctx->streams[index];

        auto media_stream = std::make_shared<MediaStream>();
        media_stream->m_stream = stream;
        media_stream->m_stream_index = index;

        m_media_stream_map[stream->index] = media_stream;
    }
}

bool InputFormatContext::CreateVideoDecoderCtx(const std::string &codec_name, const CodecConfig & param_config)
{
    bool result = false;
    for (auto & stream_data : m_media_stream_map)
    {
        auto video_media = stream_data.second; 
        if (video_media->m_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_video_decoder_ctx = std::make_shared<DecoderContext>();
            if (codec_name.empty())
            {
                result = m_video_decoder_ctx->CreateDecoderContextById(video_media->m_stream->codecpar->codec_id, param_config.m_video_config.m_hw_name);
            }
            else if (!codec_name.empty())
            {
                result = m_video_decoder_ctx->CreateDecoderContextByName(codec_name, param_config.m_video_config.m_hw_name);
            }
            if (result)
            {
                if (!param_config.m_video_config.m_hw_name.empty())
                {
                    // m_video_decoder_ctx->ConfigHw(param_config.m_video_config.m_hw_name);
                }
                // process video decoder ctx param data
                avcodec_parameters_to_context(m_video_decoder_ctx->m_decoder_ctx, video_media->m_stream->codecpar);
                // process custom config param
                //
                // 
                m_video_decoder_ctx->ConfigParam(video_media);
                result = m_video_decoder_ctx->OpenCodecCtx(); 
                if (result)
                {
                    video_media->m_decoder = m_video_decoder_ctx;
                }
            }
            else
            {
                m_video_decoder_ctx.reset();
                m_video_decoder_ctx = nullptr;
            }
            break;
        }
    }
    return result;
}

bool InputFormatContext::CreateAudioDecoderCtx(const std::string &codec_name, const CodecConfig & param_config)
{
    bool result = false;
    for (auto & stream_data : m_media_stream_map)
    {
        auto audio_media = stream_data.second; 
        if (audio_media->m_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            m_audio_decoder_ctx = std::make_shared<DecoderContext>();
            if (codec_name.empty())
            {
                result = m_audio_decoder_ctx->CreateDecoderContextById(audio_media->m_stream->codecpar->codec_id);
            }
            else
            {
                result = m_audio_decoder_ctx->CreateDecoderContextByName(codec_name);
            }
            if (result)
            {
                // process video decoder ctx param data
                avcodec_parameters_to_context(m_audio_decoder_ctx->m_decoder_ctx, audio_media->m_stream->codecpar);
                // process custom config param
                //
                //
                m_audio_decoder_ctx->ConfigParam(audio_media);
                result = m_audio_decoder_ctx->OpenCodecCtx(); 
                if (result)
                {
                    audio_media->m_decoder = m_audio_decoder_ctx;
                }
            }
            else
            {
                m_audio_decoder_ctx.reset();
                m_audio_decoder_ctx = nullptr;
            }
            break;
        }
    }
    return result;
}

void InputFormatContext::HwFrameCtxCallback(std::function<bool ()> &&callback_func)
{
    if (m_video_decoder_ctx)
    {
        // set callback
        m_video_decoder_ctx->HwFrameFunc([this](){

            m_hw_frame_callback();
            return true;
        });
        m_hw_frame_callback = std::forward<std::function<bool()>>(callback_func);
    }
}


AVFormatContext * InputFormatContext::AvFormatContext()
{
    return m_format_ctx;
}
std::shared_ptr<DecoderContext> & InputFormatContext::VideoDecoder()
{
    return m_video_decoder_ctx;
}
std::shared_ptr<DecoderContext> & InputFormatContext::AudioDecoder()
{
    return m_audio_decoder_ctx;
}
std::shared_ptr<MediaStream>     InputFormatContext::VideoMediaStream()
{
    if (m_media_stream_map.empty())
    {
        return nullptr;
    }
    return m_media_stream_map[0]->m_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? m_media_stream_map[0]:m_media_stream_map[1];
}
std::shared_ptr<MediaStream>     InputFormatContext::AudioMediaStream()
{
    if (m_media_stream_map.empty())
    {
        return nullptr;
    }
    return m_media_stream_map[0]->m_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? m_media_stream_map[1]:m_media_stream_map[0];
}

std::shared_ptr<MediaStream>     InputFormatContext::MediaStreamIndex(int index)
{
    return  m_media_stream_map[index];
}


OutputFormatContext::OutputFormatContext()
{
    m_format_ctx = nullptr;
    m_video_encoder_ctx = nullptr;
    m_audio_encoder_ctx = nullptr;
}

OutputFormatContext::~OutputFormatContext()
{
    if (m_media_stream_map.empty())
    {

    }
    if (m_format_ctx)
    {
        avformat_close_input(&m_format_ctx);
        avformat_free_context(m_format_ctx);
        PrintLog("release format source");
    }
}


bool OutputFormatContext::CreateUrl(const std::string &file_or_stream)
{
    m_format_ctx = nullptr;
    int result = avformat_alloc_output_context2(&m_format_ctx, nullptr, nullptr, file_or_stream.c_str());
    if (result < 0)
    {
        return false;
    }
    static const std::string description = "HnHjie";
    auto ptr = (uint8_t*)av_mallocz(description.size() + 2);
    memcpy(ptr, description.c_str(), description.size());
    av_dict_set(&m_format_ctx->metadata, "description", (const char*)ptr, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE);
    return true;
}

bool OutputFormatContext::CreateVideoStream(std::shared_ptr<MediaStream> media_stream, const CodecConfig & param_config)
{

    bool result = CreateVideoEncoderCtx(media_stream, "", param_config);
    if (result && m_video_encoder_ctx->GetHwInitStatus())
    {
        AVStream * new_stream = avformat_new_stream(m_format_ctx, nullptr);

        avcodec_parameters_from_context(new_stream->codecpar, m_video_encoder_ctx->AvCodecCtx());

        new_stream->avg_frame_rate = (m_video_encoder_ctx->m_encoder_ctx->framerate);
        if (new_stream->time_base.num <= 0 || new_stream->time_base.den <= 0)
            new_stream->time_base = av_add_q(m_video_encoder_ctx->m_encoder_ctx->time_base, (AVRational){0, 1});
        else
        {
            new_stream->time_base      = m_video_encoder_ctx->m_encoder_ctx->time_base;
        }
        new_stream->duration = -1;
        auto video_media_stream = std::make_shared<MediaStream>();
        video_media_stream->m_stream_index = new_stream->index;
        video_media_stream->m_stream       = new_stream;
        video_media_stream->m_encoder      = m_video_encoder_ctx;

        m_media_stream_map[video_media_stream->m_stream_index] = video_media_stream;

        std::string encoder_name;
        encoder_name = std::string(LIBAVCODEC_IDENT);
        encoder_name += " ";
        encoder_name += m_video_encoder_ctx->m_encoder_ctx->codec->name;

        PrintLog(encoder_name);
        uint8_t * ptr = (uint8_t*)av_mallocz(encoder_name.size() + 2);
        memcpy(ptr, encoder_name.c_str(), encoder_name.size());
        av_dict_set(&new_stream->metadata, "encoder", (const char*)ptr, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE);
        static const std::string description = "HnHjie";
        ptr = (uint8_t*)av_mallocz(description.size() + 2);
        memcpy(ptr, description.c_str(), description.size());
        av_dict_set(&new_stream->metadata, "description", (const char*)ptr, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE);
        if (m_video_encoder_ctx->m_encoder_ctx->nb_coded_side_data) 
        {
            for (int i = 0; i < m_video_encoder_ctx->m_encoder_ctx->nb_coded_side_data; i++)
            {
                const AVPacketSideData *sd_src = &m_video_encoder_ctx->m_encoder_ctx->coded_side_data[i];
                uint8_t *dst_data;

                dst_data = av_stream_new_side_data(new_stream, sd_src->type, sd_src->size);
                if (!dst_data)
                {
                    return AVERROR(ENOMEM);
                }
                memcpy(dst_data, sd_src->data, sd_src->size);
            }
        }
    }
    else if (result)
    {
        // set hw callback
    }
    else
    {
        printf("[%s].\n", __func__);
    }
    return result;
}

bool OutputFormatContext::CreateAudioStream(std::shared_ptr<MediaStream> media_stream, const CodecConfig & param_config)
{
    bool result = CreateAudioEncoderCtx(media_stream, "", param_config);
    if (result)
    {
        AVStream * new_stream = avformat_new_stream(m_format_ctx, nullptr);

        avcodec_parameters_from_context(new_stream->codecpar, m_audio_encoder_ctx->AvCodecCtx());

        new_stream->avg_frame_rate = (m_audio_encoder_ctx->m_encoder_ctx->framerate);
        new_stream->time_base      = m_audio_encoder_ctx->m_encoder_ctx->time_base;

        auto audio_media_stream = std::make_shared<MediaStream>();
        audio_media_stream->m_stream_index = new_stream->index;
        audio_media_stream->m_stream       = new_stream;
        audio_media_stream->m_encoder      = m_audio_encoder_ctx;

        m_media_stream_map[audio_media_stream->m_stream_index] = audio_media_stream;
    }
    return result;
}

bool OutputFormatContext::CreateVideoEncoderCtx(std::shared_ptr<MediaStream> media_stream, const std::string & codec_name, const CodecConfig & param_config)
{
    bool result = false;
    m_video_encoder_ctx = std::make_shared<EncoderContext>();
    if (!codec_name.empty())
    {
        result = m_video_encoder_ctx->CreateEncoderContextByName(codec_name, param_config.m_video_config.m_hw_name);
    }
    else
    {
        result = m_video_encoder_ctx->CreateEncoderContextById(media_stream->AvStream()->codecpar->codec_id, param_config.m_video_config.m_hw_name);
    }
    if (result)
    {
        if (param_config.m_video_config.m_copy_decoder_param)
        {
            avcodec_parameters_to_context(m_video_encoder_ctx->AvCodecCtx(), media_stream->m_stream->codecpar);
            m_video_encoder_ctx->AvCodecCtx()->time_base = media_stream->m_stream->time_base;
            m_video_encoder_ctx->AvCodecCtx()->framerate = (media_stream->m_stream->r_frame_rate);
        }
        // must
        //m_video_encoder_ctx->AvCodecCtx()->time_base = media_stream->m_stream->time_base;
        // must
        //m_video_encoder_ctx->AvCodecCtx()->framerate = (media_stream->m_stream->r_frame_rate);

        if (m_format_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            m_video_encoder_ctx->AvCodecCtx()->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        m_video_encoder_ctx->ConfigParam(param_config.m_video_config);

        result = m_video_encoder_ctx->OpenCodecCtx();
        if (!result)
        {
            m_video_encoder_ctx.reset();
        }
    }
    else
    {
        m_video_encoder_ctx.reset();
        printf("find video codec failed.\n");
    }
    return result;
}
bool OutputFormatContext::CreateAudioEncoderCtx(std::shared_ptr<MediaStream> media_stream, const std::string & codec_name, const CodecConfig & param_config)
{
    bool result = false;
    m_audio_encoder_ctx = std::make_shared<EncoderContext>();
    if (!param_config.m_audio_config.m_codec_name.empty())
    {
        result = m_audio_encoder_ctx->CreateEncoderContextByName(param_config.m_audio_config.m_codec_name);
    }
    else
    {
        result = m_audio_encoder_ctx->CreateEncoderContextById(media_stream->AvStream()->codecpar->codec_id);
    }
    if (result)
    {
        if (param_config.m_audio_config.m_copy_decoder_param)
        {
            avcodec_parameters_to_context(m_audio_encoder_ctx->AvCodecCtx(), media_stream->m_stream->codecpar);
        }
        if (media_stream)
        {
            // must
            m_audio_encoder_ctx->AvCodecCtx()->time_base    = media_stream->m_stream->time_base;
            // must
            m_audio_encoder_ctx->AvCodecCtx()->framerate = media_stream->m_stream->avg_frame_rate;
        }
        if (m_format_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            m_audio_encoder_ctx->AvCodecCtx()->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        m_audio_encoder_ctx->ConfigParam(param_config.m_audio_config);

        result = m_audio_encoder_ctx->OpenCodecCtx();
        if (!result)
        {
            m_audio_encoder_ctx.reset();
        }
    }
    else
    {
        PrintLogMsg("create eudio ncoder failed");
        m_audio_encoder_ctx.reset();
    }
    return result;
}

bool OutputFormatContext::WriterHeaderTailer()
{
    if (!(m_format_ctx->flags & AVFMT_NOFILE))
    {
        // open file
        avio_open(&m_format_ctx->pb, m_format_ctx->url, AVIO_FLAG_WRITE);
    }
    int result = avformat_write_header(m_format_ctx, nullptr);
    if (result < 0)
    {
        PrintLog(result);
        return false;
    }
    av_dump_format(m_format_ctx, 0, m_format_ctx->url, 1);
    printf("time_base : %d/%d.\n", m_format_ctx->streams[0]->time_base.num, m_format_ctx->streams[0]->time_base.den);
    return true;
}

AVFormatContext * OutputFormatContext::AvFormatContext()
{
    return m_format_ctx;
}

std::shared_ptr<EncoderContext> & OutputFormatContext::VideoEncoder()
{
    return m_video_encoder_ctx;
}

std::shared_ptr<EncoderContext> & OutputFormatContext::AudioEncoder()
{
    return m_audio_encoder_ctx;
}

std::shared_ptr<MediaStream>      OutputFormatContext::VideoMediaStream()
{
    if (m_media_stream_map.empty())
    {
        return nullptr;
    }
    return m_media_stream_map[0]->m_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? m_media_stream_map[0]:m_media_stream_map[1];
}

std::shared_ptr<MediaStream>     OutputFormatContext::AudioMediaStream()
{
    if (m_media_stream_map.empty())
    {
        return nullptr;
    }
    return m_media_stream_map[0]->m_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? m_media_stream_map[1]:m_media_stream_map[0];
}

std::shared_ptr<MediaStream>     OutputFormatContext::MediaStreamIndex(int index)
{
    return  m_media_stream_map[index];
}


bool OutputFormatContext::CreateVideoStreamAndInitHwFrameCtx(std::shared_ptr<MediaStream> media_stream)
{
    if (!m_video_encoder_ctx->m_hw_frame_init_status)
    {
        PrintLog("init hw frame ctx");

        m_video_encoder_ctx->m_hw_frame_init_status = true;

        m_video_encoder_ctx->m_encoder_ctx->pix_fmt = media_stream->m_decoder->m_decoder_ctx->pix_fmt;
        if (m_video_encoder_ctx->m_encoder_ctx->width <= 0 || m_video_encoder_ctx->m_encoder_ctx->height <= 0)
        {
            m_video_encoder_ctx->m_encoder_ctx->width   = media_stream->m_stream->codecpar->width;
            m_video_encoder_ctx->m_encoder_ctx->height  = media_stream->m_stream->codecpar->height;
        }
        for (int index = 0; ; index++)
        {
            const AVCodecHWConfig * config = avcodec_get_hw_config(m_video_encoder_ctx->m_encoder_ctx->codec, index);
            if (!config)
            {
                break;
            }
            if (config->pix_fmt == media_stream->m_decoder->m_decoder_ctx->pix_fmt)
            {
                m_video_encoder_ctx->m_encoder_ctx->pix_fmt = config->pix_fmt;
                if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
                {
                    m_video_encoder_ctx->m_encoder_ctx->hw_device_ctx = av_buffer_ref(media_stream->Decoder()->AvCodecCtx()->hw_device_ctx);
                }
                else if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
                {
                    m_video_encoder_ctx->m_encoder_ctx->hw_frames_ctx = av_buffer_ref(media_stream->Decoder()->AvCodecCtx()->hw_frames_ctx);
                    auto frames_ctx = (AVHWFramesContext *)(m_video_encoder_ctx->m_encoder_ctx->hw_frames_ctx->data);
                    frames_ctx->format    = AV_PIX_FMT_NV12;
                    frames_ctx->sw_format = AV_PIX_FMT_NV12;
                    frames_ctx->width     = m_video_encoder_ctx->m_encoder_ctx->width;
                    frames_ctx->height    = m_video_encoder_ctx->m_encoder_ctx->height;
                    frames_ctx->initial_pool_size = 20;
                }
                break;
            }
        }
        bool result = m_video_encoder_ctx->OpenCodecCtx();
        if (result)
        {
            AVStream * new_stream = avformat_new_stream(m_format_ctx, nullptr);

            avcodec_parameters_from_context(new_stream->codecpar, m_video_encoder_ctx->AvCodecCtx());
            printf("%d/%d.\n", m_video_encoder_ctx->m_encoder_ctx->framerate.num, m_video_encoder_ctx->m_encoder_ctx->framerate.den);
            new_stream->avg_frame_rate = m_video_encoder_ctx->m_encoder_ctx->framerate;
            new_stream->time_base      = m_video_encoder_ctx->m_encoder_ctx->time_base;

            auto video_media_stream = std::make_shared<MediaStream>();
            video_media_stream->m_stream_index = new_stream->index;
            video_media_stream->m_stream       = new_stream;
            video_media_stream->m_encoder      = m_video_encoder_ctx;
            video_media_stream->m_stream->codecpar->format = m_video_encoder_ctx->m_encoder_ctx->pix_fmt;

            m_media_stream_map[video_media_stream->m_stream_index] = video_media_stream;

            WriterHeaderTailer();
        }
        else
        {
            PrintLog("open encoder ctx failed");
        }
        return result;
    }
    return true;
}

bool OutputFormatContext::CreateVideoStreamAndInitHwCtx(AVFilterContext * filter_ctx)
{
    if (!m_video_encoder_ctx->m_hw_frame_init_status)
    {
        PrintLog("init hw frame ctx");

        m_video_encoder_ctx->m_hw_frame_init_status = true;

        for (int index = 0; ; index++)
        {
            const AVCodecHWConfig * config = avcodec_get_hw_config(m_video_encoder_ctx->m_encoder_ctx->codec, index);
            if (!config)
            {
                break;
            }
            if (config->pix_fmt == av_buffersink_get_format(filter_ctx))
            {
                m_video_encoder_ctx->m_encoder_ctx->pix_fmt = config->pix_fmt;
                if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
                {
                    m_video_encoder_ctx->m_encoder_ctx->hw_device_ctx = HwInstance::Instance()->GetBuffer();
                }
                else if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX && av_buffersink_get_hw_frames_ctx(filter_ctx))
                {
                    m_video_encoder_ctx->m_encoder_ctx->hw_frames_ctx = av_buffer_ref(av_buffersink_get_hw_frames_ctx(filter_ctx));
                    auto frames_ctx = (AVHWFramesContext *)(m_video_encoder_ctx->m_encoder_ctx->hw_frames_ctx->data);
                    frames_ctx->format    = AV_PIX_FMT_NV12;
                    frames_ctx->sw_format = AV_PIX_FMT_NV12;
                    frames_ctx->width     = m_video_encoder_ctx->m_encoder_ctx->width;
                    frames_ctx->height    = m_video_encoder_ctx->m_encoder_ctx->height;
                    frames_ctx->initial_pool_size = 20;
                }
                break;
            }
        }
        bool result = m_video_encoder_ctx->OpenCodecCtx();
        if (result)
        {
            AVStream * new_stream = avformat_new_stream(m_format_ctx, nullptr);

            avcodec_parameters_from_context(new_stream->codecpar, m_video_encoder_ctx->AvCodecCtx());
            printf("%d/%d.\n", m_video_encoder_ctx->m_encoder_ctx->framerate.num, m_video_encoder_ctx->m_encoder_ctx->framerate.den);
            new_stream->avg_frame_rate = m_video_encoder_ctx->m_encoder_ctx->framerate;
            new_stream->time_base      = m_video_encoder_ctx->m_encoder_ctx->time_base;

            auto video_media_stream = std::make_shared<MediaStream>();
            video_media_stream->m_stream_index = new_stream->index;
            video_media_stream->m_stream       = new_stream;
            video_media_stream->m_encoder      = m_video_encoder_ctx;
            video_media_stream->m_stream->codecpar->format = m_video_encoder_ctx->m_encoder_ctx->pix_fmt;

            m_media_stream_map[video_media_stream->m_stream_index] = video_media_stream;

            WriterHeaderTailer();
        }
        else
        {
            PrintLog("open encoder ctx failed");
        }
        return result;
    }
    return true;
}

void OutputFormatContext::AddMetaDataToContext(std::map<std::string, std::string> & key_value)
{
    for (auto & data : key_value)
    {
        auto * p = (uint8_t*)av_mallocz(sizeof(data.second.c_str()) + 2);
        memcpy(p, data.second.c_str(), data.second.size());
        std::string key = data.first;
        av_dict_set(&m_format_ctx->metadata, key.c_str(), (const char*)p, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE);
    }
}


